/************************************************************************
  DISCLAIMER:
  This software was produced by the National Institute of Standards
  and Technology (NIST), an agency of the U.S. government, and by statute
  is not subject to copyright in the United States.  Recipients of this
  software assume all responsibility associated with its operation,
  modification, maintenance, and subsequent redistribution.

  See NIST Administration Manual 4.09.07 b and Appendix I.
************************************************************************/

#include <string.h>  // for strlen, strcpy, strcat
#include <stdio.h>   // for fopen, etc.
#include <stdlib.h>  // for exit
#include "dmis.hh"
#include "cs1Lists.cc"
#include "cs2Lists.cc"
#include "cs3Lists.cc"
#include "ipv1Lists.cc"
#include "ipv2Lists.cc"
#include "ipv3Lists.cc"
#include "mc1Lists.cc"
#include "mc2Lists.cc"
#include "mc3Lists.cc"
#include "p1Lists.cc"
#include "p2Lists.cc"
#include "p3Lists.cc"
#include "qis1Lists.cc"
#include "qis2Lists.cc"
#include "qis3Lists.cc"
#include "rt1Lists.cc"
#include "rt2Lists.cc"
#include "rt3Lists.cc"
#include "sga1Lists.cc"
#include "sga2Lists.cc"
#include "sga3Lists.cc"
#include "tw1Lists.cc"
#include "tw2Lists.cc"
#include "tw3Lists.cc"
#include "unc1Lists.cc"
#include "unc2Lists.cc"
#include "unc3Lists.cc"

namespace NDTS{
  extern int numErrors;
  extern int numWarnings;
  extern inputFile * tree;
  void parseDmis(char * fileName);
}
namespace NDTU {
  using namespace NDTS;

/*

warnAtt and warnSub are not functions (with toPrint a dmisFullCppBase)
because when toPrint has multiple supertypes, ambiguous subtype errors
are caused by using the printSelf function.

For both macros, the call should be preceded by a left brace and
followed by a semicolon and a right brace (so the macro call looks a
normal function call).

The if(1){...}else alternative for this style of macro is not better
because it is more complex and still requires braces to avoid a
compiler warning if it is used after an "if" that does not have an
"else".

*/

#define warnAtt(attrib, className, toPrint)              \
   numErrors++;                                          \
   printf("no %s attribute of %s\n", attrib, className); \
   toPrint->printSelf();                                 \
   printf("\n\n")

#define warnAttBlock(statement, toPrint)		      \
   numErrors++;                                               \
   printf("%s is not in the conformance class\n", statement); \
   toPrint->printSelf();                                      \
   printf("\n")

#define warnSub(sub, super, toPrint)            \
   numErrors++;                                 \
   printf("no %s subtype of %s\n", sub, super); \
   toPrint->printSelf();                        \
   printf("\n\n")

#define warnBlockBug(sub, super)               \
   printf("bug: %s subtype of %s should be "   \
          "allowed but isn't\n\n", sub, super) 

/********************************************************************/

/*

The term "conformance module" is used here to mean the contents of a
level (1, 2, or 3) of [an AP (prismatic (PM) or thin-walled (TW)) or
an addendum (rotary table (RY), multiple-carriage (MC), contact
scanning (CT), ipv (IP), qis (QI), measurement uncertainty (MU), or
soft gaging (SF))].

This file has two top-level functions (recordConformance and testDmis)
that are intended to be called from drivers. They both determine the
conformance levels of a DMIS program. RecordConformance revises the
program by putting a conformance statement on the DMISMN or DMISMD
line of the file; it does not allow the user to enter levels in the
arguments. TestDmis allows the user to enter levels in the arguments.
It does not revise the input file, it only prints conformance data to
stdout. TestDmis also prints the name of the DMIS item that first
forced the level of each conformance module to its final value. Users
are expected to find this information useful since without it, they
may wonder why the levels of the conformance modules were set as
shown.

Each DMIS program is tested for both its PM level and its TW level. In
testDmis, only the one (PM or TW) used in the command arguments is reported.
In recordConformance, both or only one of PM and TW will be recorded,
as determined by the arguments.

PM4 and TW4 are used to designate programs that use commands not in any
level of the AP and not in any addendum.

There is a problem because intFuncPtdataAtts_a_faLabel is allowed at
level 2 of CT and at level 3 of PM and TW. In this case, CT,2 is an
alternative to PM,3 or TW,3. Thus, for some programs, a decision must
be made whether to use PM(or TW),3 or both PM(or TW),2 and CT,2 (There
is no other case of an item being allowed at one level in an AP and at
a lower level in an addendum.

This is handled as follows:
a. The global variable funcPtdataFlag is used and initialized to 0.
b. The dmisConformanceTester.cc file is edited by hand as follows
    before being compiled.
   i. In the check_intFuncPtdata function, delete or comment out the line:
      adjustLevels(&intFuncPtdataAtts_a_faLabel, "intFuncPtdata a_faLabel");
   ii. Insert in lieu thereof:
      funcPtdataFlag = 1;
c. The resetCurrentLevels function checks the funcPtdataFlag and may change
   the levels for PM and TW. See the documentation of resetCurrentLevels.

*/


const char ** masterSubAtts[1650];
int funcPtdataFlag = 0; 

typedef struct levelsStruct
{
  int p;
  int tw;
  int rt;
  int mc;
  int cs;
  int ipv;
  int qis;
  int unc;
  int sga;
} levels;

/*

levelForcers are the names of the classes that first force the
levels of the APs and addenda to their final levels.

currentLevels are the minimum required levels of the conformance modules.
See documentation of adjustLevels regarding how currentLevels and
levelForcers are updated.

*/

const char * levelForcers[9] = {0,0,0,0,0,0,0,0};
levels currentLevels = {0,0,0,0,0,0,0,0,0};
levels preferredLevels = {0,0,0,0,0,0,0,0,0};

} // namespace NDTU

#include "levelsSet.cc"

namespace NDTU {
  using namespace NDTS;

/********************************************************************/

// function defined in assignModuleSubAtts.cc
void assignModuleSubAtts();
// function defined in assignMasterSubAtts.cc
void assignMasterSubAtts();

// function defined in dmisConformanceTester.cc
void check_inputFile(inputFile * a_inputFile, int log);

// functions defined here hard-coded
void addAtEnd(const char ** addTo, const char ** from);
void adjustLevels(levels * levs, char * className);
void checkOneFile(char * fileName, char pOrT, bool useQ);
void combineLists(const char *** mainList, const char **** otherLists,
		  int otherListsSize);
bool dupAddendum(const char *** list1, const char *** list2,
		 const char *** list3, const char **** otherLists,
		 int otherListsSize);
bool findString(const char * toFind, const char * lookIn[]);
bool findString2(const char * toFind, char * lookIn[], int stop);
void insertConformanceStatement(char * fileName, char * confStatement);
void installSubAtts(const char *** moduleSubAtts);
bool prepareLists(int addenda, char ** args);
int recordConformance(int argc, char * argv[]);
void resetCurrentLevels();
void setLevsArray(char pOrT, bool useQ, char * levs);
void showLevelForcers(char pOrT, bool useQ);
void sprintLevel(char * levs, int * k, const char * mod, int level);
int testDmis(int argc, char * argv[]);

/********************************************************************/

/* addAtEnd

Returned Value: none

Called By: installSubAtts

This copies the string pointers of the from array that do not have the
same text as any element of the addTo array onto the end of the addTo
array (i.e. it does not copy duplicates). This starts with the second
string in the from array since the first string represents the array
name. This is not checking array sizes.  Both arrays must be
zero-terminated.

The strings themselves are not copied.

*/
void addAtEnd(        /* ARGUMENTS                    */
 const char ** addTo, /* array of strings to add to   */
 const char ** from)  /* array of strings to add from */
{
  int m;  // index for addTo
  int n;  // index from from

  for (n = 1; from[n]; n++)
    {
      for (m = 1; addTo[m]; m++)
	{
	  if (strcmp(from[n], addTo[m]) == 0) // from[n] is same as addTo[m]
	    break;
 	}
      if (addTo[m]) // found a duplicate so do not add it
	continue;
      else // from[n] is not a duplicate, so add it to end of addTo list
	{
	  addTo[m] = from[n];
	  addTo[m+1] = 0;
	}
    }
}

/********************************************************************/

/* adjustLevels

Returned Value: none

Called By: hundreds of functions in dmisConformanceTester.cc

A. In general:
1. The values in levs and currentLevels are usually integers
   between 0 and 3.
2. If a class is in full DMIS but not in any AP or addendum, then
   the value for p and tw will be 4.
3. Whenever the levs for a particular AP or addendum are higher than the
   currentLevels for that module, the currentLevels for that module is
   raised in this function to the value in levs.

There is a problem with this if two modules other than p and tw both
include a particular class because then the modules may be
alternatives. We don't want to report both as required if only one is
needed. p and tw are already alternatives. The only instance of this
is ipv and qis. Seven commands are in both modules (CLMPID(2),
CLMPSN(2), FIXTID(2), FIXTSN(2), PARTID(1), PARTRV(1), PARTSN(1)).

To deal with the ipv/qis problem, the entries in the levels for those
commands are negative, and the updating of currentLevels.ipv and
currentLevels.qis is done as follows:

1. If the current entry is 0, then it is replaced by the levs entry.
2. If the current entry is negative and the levs entry is negative,
   then the current entry is replaced by the levs entry if the levs
   entry is larger in absolute value.
3. If the current entry is negative and the levs entry is positive,
   then the current entry is replaced by the larger of the absolute
   value of the current entry and the levs entry.
4. If the current entry is positive and the levs entry is negative,
   then if the absolute value of the levs entry is larger than the
   current entry, the current entry is replaced by the absolute value
   of the levs entry.
5. If the current entry is positive and the levs entry is positive,
   then if the levs entry is larger than the current entry, the current
   entry is replaced by the levs entry.
Then when reporting is done:
1. If currentLevels.ipv and currentLevels.qis are both negative, that
   means that only commands included in both modules occurred in the
   program being tested. In this case the levels will be the same for
   ipv and qis. Since either module could be reported but ipv requires
   fewer commands than qis, only ipv is reported as being required.
2. If one module is positive and the other is negative, only the one
   that is positive will be reported as being required.
3. If the currentLevels values for both addenda are positive, both addenda
   are reported as being required. This is not ideal because if both of
   them would be at level 1 except for the shared level 2 commands, it
   would suffice to require level 1 for one addendum and level 2 for the
   other addendum. That is minor, however.

*/

void adjustLevels(       /* ARGUMENTS                 */
 levels * levs,          /* levels struct for a class */
 const char * className) /* name of the class         */
{
  if (levs->p > currentLevels.p)
    {
      currentLevels.p = levs->p;
      levelForcers[0] = className;
    }
  if (levs->tw > currentLevels.tw)
    {
      currentLevels.tw = levs->tw;
      levelForcers[1] = className;
    }
  if (levs->rt > currentLevels.rt)
    {
      currentLevels.rt = levs->rt;
      levelForcers[2] = className;
    }
  if (levs->mc > currentLevels.mc)
    {
      currentLevels.mc = levs->mc;
      levelForcers[3] = className;
    }
  if (levs->cs > currentLevels.cs)
    {
      currentLevels.cs = levs->cs;
      levelForcers[4] = className;
    }
  if (currentLevels.ipv == 0)
    { 
      if (levs->ipv)
	{
	  currentLevels.ipv = levs->ipv;
	  levelForcers[5] = className;
	}
    }
  else if (currentLevels.ipv < 0)
    {
      if (levs->ipv < currentLevels.ipv)
	{
	  currentLevels.ipv = levs->ipv;
	  levelForcers[5] = className;
	}
      else if (levs->ipv > 0)
	{
	  currentLevels.ipv = ((-currentLevels.ipv > levs->ipv) ?
			       -currentLevels.ipv : levs->ipv);
	  levelForcers[5] = className;
	}
    }
  else // if (currentLevels.ipv > 0)
    {
      if ((levs->ipv < 0) && (-levs->ipv > currentLevels.ipv))
	{
	  currentLevels.ipv = -levs->ipv;
	  levelForcers[5] = className;
	}
      else if ((levs->ipv > 0) && (levs->ipv > currentLevels.ipv))
	{
	  currentLevels.ipv = levs->ipv;
	  levelForcers[5] = className;
	}
    }
  if (currentLevels.qis == 0)
    { 
      if (levs->qis)
	{
	  currentLevels.qis = levs->qis;
	  levelForcers[6] = className;
	}
    }
  else if (currentLevels.qis < 0)
    {
      if (levs->qis < currentLevels.qis)
	{
	  currentLevels.qis = levs->qis;
	  levelForcers[6] = className;
	}
      else if (levs->qis > 0)
	{
	  currentLevels.qis = ((-currentLevels.qis > levs->qis) ?
			       -currentLevels.qis : levs->qis);
	  levelForcers[6] = className;
	}
    }
  else // if (currentLevels.qis > 0)
    {
      if ((levs->qis < 0) && (-levs->qis > currentLevels.qis))
	{
	  currentLevels.qis = -levs->qis;
	  levelForcers[6] = className;
	}
      else if ((levs->qis > 0) && (levs->qis > currentLevels.qis))
	{
	  currentLevels.qis = levs->qis;
	  levelForcers[6] = className;
	}
    }
  if (levs->unc > currentLevels.unc)
    {
      currentLevels.unc = levs->unc;
      levelForcers[7] = className;
    }
  if (levs->sga > currentLevels.sga)
    {
      currentLevels.sga = levs->sga;
      levelForcers[8] = className;
    }
}

/********************************************************************/

/* checkOneFile

Returned Value: none

Called By:  testDmis

This checks one DMIS input file as follows:
1. Call parseDmis, which checks for syntax errors. If there are no errors,
   the parser also builds a parse tree named "tree".
2. Report the number of errors and warnings for the file.
3. If there are no errors:
   a. Call check_inputFile, which checks whether the tree contains items
      that are not in the conformance class. For each item used but not in
      the conformance class (if there are any such items), an error message
      is printed and the class that caused the error is printed out.
   b. Report the number of conformance class errors found in the file.
   c. Call resetCurrentLevels to possible reset the currentLevels for p and tw.
   d. Call setLevsArray
   e. Print the levs array
   f. Call showLevelForcers to report the items that forced the levels.

*/

void checkOneFile(      /* ARGUMENTS                                       */
 char * dmiName,        /* name of DMIS input file to check                */
 char pOrT,             /* P means report prismatic; T means thin-walled   */
 bool useQ)             /* true = report qis if choice; false = report ipv */
{
  char levs[80];

  parseDmis(dmiName);
  printf("%d parser error%s\n", numErrors, ((numErrors == 1) ? "" : "s"));
  printf("%d parser warning%s\n\n", numWarnings,
	  ((numWarnings == 1) ? "" : "s"));
  if (numErrors == 0)
    {
      check_inputFile(tree, 1);
      printf("%d conformance checker error%s\n\n",
	      numErrors, ((numErrors == 1) ? "" : "s"));
      resetCurrentLevels();
      setLevsArray(pOrT, useQ, levs);
      printf("%s\n", levs);
      showLevelForcers(pOrT, useQ);
    }
}

/********************************************************************/

/* combineLists

Returned Value: none

Called By: prepareLists

This copies the mainSubAtts into masterSubAtts and then copies each
otherSubAtts into masterSubAtts. See documentation of installSubAtts.

*/

void combineLists(             /* ARGUMENTS                                   */
 const char *** mainSubAtts,   /* array of arrays of strings to start with    */
 const char **** otherSubAtts, /* array of arrays of arrays of strings to add */
 int otherSubAttsSize)         /* size of otherSubAtts                        */
{
  int n;

  installSubAtts(mainSubAtts);
  for (n = 0; n < otherSubAttsSize; n++)
    {
      installSubAtts(otherSubAtts[n]);
    }
}

/********************************************************************/

/* dupAddendum

Returned Value: bool
If any of the arrays list1, list2, and list3 is already in the otherLists,
this returns true (indicating a duplicate). Otherwise it returns false.

Called By: prepareLists

*/

bool dupAddendum(            /* ARGUMENTS             */
 const char *** list1,       /* first list to check   */
 const char *** list2,       /* second list to check  */
 const char *** list3,       /* third list to check   */
 const char **** otherLists, /* list to look in       */
 int otherListsSize)         /* size of otherLists    */
{
  int n;

  for (n = 0; n < otherListsSize; n++)
    {
      if ((otherLists[n] == list1) ||
	  (otherLists[n] == list2) ||
	  (otherLists[n] == list3))
	return true;
    }
  return false;
}

/********************************************************************/

/* findString

Returned Value: bool
If toFind is found in lookIn, this returns true.
Otherwise, it returns false.

Called By:
  hundreds of checking functions in dmisConformanceTester.cc

FindString does string comparison using strcmp. It is not looking for
string identity.

FindString is used only on short arrays, so it is efficient enough.

For findString to work, the last entry in lookIn must be 0.

*/

bool findString(        /* ARGUMENTS                   */
 const char * toFind,   /* string to find              */
 const char * lookIn[]) /* array of strings to look in */
{
  const char ** wordPtr;

  for (wordPtr = lookIn; *wordPtr; wordPtr = (wordPtr + 1))
    {
      if (strcmp(toFind, *wordPtr) == 0)
	return true;
    }
  return false;
}

/********************************************************************/

/* findString2

Returned Value: bool
If toFind is found in lookIn, starting at the third string of lookIn,
this returns true. Otherwise, it returns false.

Called By:  testDmis

This does string comparison using strcmp. It is not looking for
string identity.

*/

bool findString2(      /* ARGUMENTS                   */
 const char * toFind,  /* string to find              */
 char * lookIn[],      /* array of strings to look in */
 int stop)
{
  int n;

  for (n = 2; n < stop; n++)
    {
      if (strcmp(toFind, lookIn[n]) == 0)
	return true;
    }
  return false;
}

/********************************************************************/

/* insertConformanceStatement

Returned Value: none

Called By: recordConformance

This inserts a conformance statement on the DMISMN or DMISMD line in
the file whose name is fileName.

If the line is continued (last visible character is a $), this prints
an error message and does not insert the conformance statement.

If the version number on the line is not 05.2,  this prints
an error message and does not insert the conformance statement.

If the line already contains a conformance statement, it is replaced.

If there is any problem with opening or renaming a file, this prints
an error message and does not insert the conformance statement.

This is assuming that the file has already been parsed without error,
so it is not checking for errors.

By adding a conformance statement, this could print a line that is
too long (more than 65536 characters), but that is so unlikely
it is not being checked.

*/

void insertConformanceStatement( /* ARGUMENTS                              */
 char * fileName,         /* file in which to insert conformance statement */
 char * confStatement)    /* conformance statement to insert               */
{
  FILE * inFile;
  FILE * outFile;
  char backupName[256];
  char line[65540];
  int k;                // counter for characters on DMISMN line
  int n;                // counter for file lines

  sprintf(backupName, "%s.back", fileName);
  remove(backupName); 
  if (rename(fileName, backupName))
    {
      printf("could not save backup file\n");
      printf("conformance statement not generated\n");
      printf("exiting NIST DMIS Conformance Recorder\n");
      return;
    }
  inFile = fopen(backupName, "rb");
  if (inFile == 0)
    {
      printf("could not open %s for reading\n", backupName);
      printf("conformance statement not generated\n");
      printf("exiting NIST DMIS Conformance Recorder\n");
      rename(backupName, fileName);
      return;
    }
  outFile = fopen(fileName, "wb");
  if (outFile == 0)
    {
      printf("could not open %s for writing\n", fileName);
      printf("conformance statement not generated\n");
      printf("exiting NIST DMIS Conformance Recorder\n");
      fclose(inFile);
      rename(backupName, fileName);
      return;
    }
  n = 0;
  while (fgets(line, 65538, inFile))
    {
      n++;
      for (k = 0; ((line[k] == ' ') || (line[k] == '\t')); k++);
      if (((line[k]   == 'D') || (line[k]   == 'd')) &&
	  ((line[k+1] == 'M') || (line[k+1] == 'm')) &&
	  ((line[k+2] == 'I') || (line[k+2] == 'i')) &&
	  ((line[k+3] == 'S') || (line[k+3] == 's')) &&
	  ((line[k+4] == 'M') || (line[k+4] == 'm')) &&
	  ((line[k+5] == 'D') || (line[k+5] == 'd') ||
	   (line[k+5] == 'N') || (line[k+5] == 'n')))
	break;
      fprintf(outFile, "%s", line);
    }
  for (k = (strlen(line) - 3); ((line[k] == ' ') || (line[k] == '\t')); k--);
  if (line[k] == '$')
    {
      printf("DMISMN or DMISMD line is continued\n");
      printf("conformance statement not generated\n");
      printf("exiting NIST DMIS Conformance Recorder\n");
      fclose(inFile);
      fclose(outFile);
      remove(fileName);
      rename(backupName, fileName);
      return;
    }
  for (; k > 6; k--)
    {
      line[k+1] = 0;
      if (strcmp((line + k - 3), "05.2") == 0)
	break;
    }
  if (k == 6)
    {
      printf("DMIS version number is not 05.2\n");
      printf("conformance statement not generated\n");
      printf("exiting NIST DMIS Conformance Recorder\n");
      fclose(inFile);
      fclose(outFile);
      remove(fileName);
      rename(backupName, fileName);
      return;
    }
  fprintf(outFile, "%s, %s%c%c", line, confStatement, 13, 10);
  while (fgets(line, 65538, inFile))
    {
      n++;
      fprintf(outFile, "%s", line);
    }
  fclose(inFile);
  fclose(outFile);
  printf("parsed all %d lines of file - no parser errors\n", n);
  printf("conformance statement \"%s\" inserted at end of DMISMN line\n",
	 confStatement);
  printf("original file copied to %s\n", backupName);
  printf("exiting NIST DMIS Conformance Recorder\n");
}

/********************************************************************/

/* installSubAtts

Returned Value: none

Called By: combineLists

This copies each sublist of the moduleSubAtts into the corresponding
sublist of masterSubAtts.

For this to work:
1. The sublists of both the moduleSubAtts and masterSubAtts must be in
   alphabetical order of the first entry in the sublist.
2. Every sublist of the moduleSubAtts must have a counterpart in masterSubAtts
   (but masterSubAtts may have sublists with no counterpart in moduleSubAtts).

The first entry in each sublist of masterSubAtts is a string that is the
same as the global variable name of the sublist. The first entry in
each sublist of moduleSubAtts is also the same as a global variable name
of some sublist. The two first entries are used to match sublists of
moduleSubAtts with sublists of masterSubAtts.

*/

void installSubAtts(           /* ARGUMENTS                              */
 const char *** moduleSubAtts) /* array of sublists to merge with master */
{
  int m;  // index for moduleSubAtts
  int n;  // index for masterSubAtts
  
  n = 0;
  for (m = 0; moduleSubAtts[m]; m++)
    {
      for (; masterSubAtts[n]; n++)
	{
	  if (strcmp(moduleSubAtts[m][0], masterSubAtts[n][0]) == 0)
	    {
	      addAtEnd(masterSubAtts[n], moduleSubAtts[m]);
	      break;
	    }
	}
    }
}

/********************************************************************/

/* prepareLists

Returned Value: bool
If any of the command arguments is bad, this returns false.
Otherwise, it returns true.

Called By: testDmis

This checks that the second command argument is one of the allowed
6 values.

This also checks that each additional argument (which indicates
a specific level of a specific addendum) is one of the allowed 21
values and that at most one of each group of three is used. This has
the side effect of ensuring that there are not too many entries in the
otherLists array.

If the command arguments are OK, this sets the mainList and the
otherLists arrays and calls combineLists, which will use them to
install the contents of the sublists of masterSubAtts (a global array).

*/

bool prepareLists( /* ARGUMENTS                                     */
 int addenda,      /* number of addenda at end of command arguments */
 char ** args)     /* end of array of command arguments             */
{
  int k; // arg counter
  const char *** mainList;
  const char *** otherLists[9];

  assignModuleSubAtts();
  assignMasterSubAtts();
  if (strcmp(args[0], "PM1") == 0)
    {
      mainList = p1Lists;
      preferredLevels.p = 1;
    }
  else if (strcmp(args[0], "PM2") == 0)
    {
      mainList = p2Lists;
      preferredLevels.p = 2;
    }
  else if (strcmp(args[0], "PM3") == 0)
    {
      mainList = p3Lists;
      preferredLevels.p = 3;
    }
  else if (strcmp(args[0], "TW1") == 0)
    {
      mainList = tw1Lists;
      preferredLevels.tw = 1;
    }
  else if (strcmp(args[0], "TW2") == 0)
    {
      mainList = tw2Lists;
      preferredLevels.tw = 2;
    }
  else if (strcmp(args[0], "TW3") == 0)
    {
      mainList = tw3Lists;
      preferredLevels.tw = 3;
    }
  else
    return false;
  args = (args + 1);
  for (k = 0; k < addenda; k++)
    {
      if (strcmp(args[k], "CT1")  == 0)
	{
	  if (dupAddendum(cs1Lists, cs2Lists, cs3Lists, otherLists, k))
	    return false;
	  otherLists[k] = cs1Lists;
	  preferredLevels.cs = 1;
	}
      else if (strcmp(args[k], "CT2")  == 0)
	{
	  if (dupAddendum(cs1Lists, cs2Lists, cs3Lists, otherLists, k))
	    return false;
	  otherLists[k] = cs2Lists;
	  preferredLevels.cs = 2;
	}
      else if (strcmp(args[k], "CT3")  == 0)
	{
	  if (dupAddendum(cs1Lists, cs2Lists, cs3Lists, otherLists, k))
	    return false;
	  otherLists[k] = cs3Lists;
	  preferredLevels.cs = 3;
	}
      else if (strcmp(args[k], "IP1")  == 0)
	{
	  if (dupAddendum(ipv1Lists, ipv2Lists, ipv3Lists, otherLists, k))
	    return false;
	  otherLists[k] = ipv1Lists;
	  preferredLevels.ipv = 1;
	}
      else if (strcmp(args[k], "IP2")  == 0)
	{
	  if (dupAddendum(ipv1Lists, ipv2Lists, ipv3Lists, otherLists, k))
	    return false;
	  otherLists[k] = ipv2Lists;
	  preferredLevels.ipv = 2;
	}
      else if (strcmp(args[k], "IP3")  == 0)
	{
	  if (dupAddendum(ipv1Lists, ipv2Lists, ipv3Lists, otherLists, k))
	    return false;
	  otherLists[k] = ipv3Lists;
	  preferredLevels.ipv = 3;
	}
      else if (strcmp(args[k], "MC1")  == 0)
	{
	  if (dupAddendum(mc1Lists, mc2Lists, mc3Lists, otherLists, k))
	    return false;
	  otherLists[k] = mc1Lists;
	  preferredLevels.mc = 1;
	}
      else if (strcmp(args[k], "MC2")  == 0)
	{
	  if (dupAddendum(mc1Lists, mc2Lists, mc3Lists, otherLists, k))
	    return false;
	  otherLists[k] = mc2Lists;
	  preferredLevels.mc = 2;
	}
      else if (strcmp(args[k], "MC3")  == 0)
	{
	  if (dupAddendum(mc1Lists, mc2Lists, mc3Lists, otherLists, k))
	    return false;
	  otherLists[k] = mc3Lists;
	  preferredLevels.mc = 3;
	}
      else if (strcmp(args[k], "QI1")  == 0)
	{
	  if (dupAddendum(qis1Lists, qis2Lists, qis3Lists, otherLists, k))
	    return false;
	  otherLists[k] = qis1Lists;
	  preferredLevels.qis = 1;
	}
      else if (strcmp(args[k], "QI2")  == 0)
	{
	  if (dupAddendum(qis1Lists, qis2Lists, qis3Lists, otherLists, k))
	    return false;
	  otherLists[k] = qis2Lists;
	  preferredLevels.qis = 2;
	}
      else if (strcmp(args[k], "QI3")  == 0)
	{
	  if (dupAddendum(qis1Lists, qis2Lists, qis3Lists, otherLists, k))
	    return false;
	  otherLists[k] = qis3Lists;
	  preferredLevels.qis = 3;
	}
      else if (strcmp(args[k], "RY1")  == 0)
	{
	  if (dupAddendum(rt1Lists, rt2Lists, rt3Lists, otherLists, k))
	    return false;
	  otherLists[k] = rt1Lists;
	  preferredLevels.rt = 1;
	}
      else if (strcmp(args[k], "RY2")  == 0)
	{
	  if (dupAddendum(rt1Lists, rt2Lists, rt3Lists, otherLists, k))
	    return false;
	  otherLists[k] = rt2Lists;
	  preferredLevels.rt = 2;
	}
      else if (strcmp(args[k], "RY3")  == 0)
	{
	  if (dupAddendum(rt1Lists, rt2Lists, rt3Lists, otherLists, k))
	    return false;
	  otherLists[k] = rt3Lists;
	  preferredLevels.rt = 3;
	}
      else if (strcmp(args[k], "SF1")  == 0)
	{
	  if (dupAddendum(sga1Lists, sga2Lists, sga3Lists, otherLists, k))
	    return false;
	  otherLists[k] = sga1Lists;
	  preferredLevels.sga = 1;
	}
      else if (strcmp(args[k], "SF2")  == 0)
	{
	  if (dupAddendum(sga1Lists, sga2Lists, sga3Lists, otherLists, k))
	    return false;
	  otherLists[k] = sga2Lists;
	  preferredLevels.sga = 2;
	}
      else if (strcmp(args[k], "SF3")  == 0)
	{
	  if (dupAddendum(sga1Lists, sga2Lists, sga3Lists, otherLists, k))
	    return false;
	  otherLists[k] = sga3Lists;
	  preferredLevels.sga = 3;
	}
      else if (strcmp(args[k], "MU1")  == 0)
	{
	  if (dupAddendum(unc1Lists, unc2Lists, unc3Lists, otherLists, k))
	    return false;
	  otherLists[k] = unc1Lists;
	  preferredLevels.unc = 1;
	}
      else if (strcmp(args[k], "MU2")  == 0)
	{
	  if (dupAddendum(unc1Lists, unc2Lists, unc3Lists, otherLists, k))
	    return false;
	  otherLists[k] = unc2Lists;
	  preferredLevels.unc = 2;
	}
      else if (strcmp(args[k], "MU3")  == 0)
	{
	  if (dupAddendum(unc1Lists, unc2Lists, unc3Lists, otherLists, k))
	    return false;
	  otherLists[k] = unc3Lists;
	  preferredLevels.unc = 3;
	}
      else
	return false;
    }
  combineLists(mainList, otherLists, k);
  return true;
}

/********************************************************************/

/* recordConformance

Returned Value: int
If there is an error in the arguments, then this returns 1.
Otherwise, it returns 0.

Called By: not called here. Intended to be called in some other file.

This records a conformance statement in the DMISMD or DMISMN DMIS
statement that must be the first non-comment line in a DMIS input file.

This takes one to four arguments. The first argument must be the name
of a DMIS input file. The other three arguments are optional.

The first optional argument is PM. The second optional argument is TW.
If both of those or neither of those arguments is given, both PM and
TW levels will be recorded in the revised DMIS input file. If only PM
is given, only the PM conformance level will be recorded. If only TW
is given, only the TW conformance level will be recorded.

The third optional argument is QI. The useQ flag is set to 1 QI is
an argument. See documentation of setLevsArray regarding how useQ
affects what is recorded.

This:
a. checks that there is at least one and not more than four arguments.

b. sets printPM, printTW, and useQ.

c. calls parseDmis to parse the file whose name is argv[1] and check for
   syntax errors, returning 1 if there are any syntax errors.

d. calls check_inputFile with checking against a proposed set of 
   conformance levels turned off.

e. calls resetCurrentLevels to deal with the intFuncPtdataAtts_a_faLabel
   problem mentioned above.

f. calls setLevsArray and fiddles a little to set the levs2 string.

g. calls insertConformanceStatement to put the levs2 string into the file.
   
h. returns 0.

*/

int recordConformance( /* ARGUMENTS                                   */
 int argc,             /* number of command arguments                 */
 char * argv[])        /* array of command name and command arguments */
{
  char levs1[80];  // conformance string using PM
  char levs2[80];  // conformance string using TW
  int printPM;     // 1=print PM level, 0=not
  int printTW;     // 1=print TW level, 0=not
  int useQ;        // 1=use QI, not IP, if there is a choice
  int n;           // counter for arguments
  FILE * test;     // to check whether file can be opened

  printf("running command \"dmisConformanceRecorder");
  for (n = 1; n < argc; n++)
    printf(" %s", argv[n]);
  printf("\"\n\n");
  printf("starting NIST DMIS Conformance Recorder, Version 2.2.1\n");
  printf("for DMIS Version 5.2\n");
  if ((argc < 2) || (argc > 5))
    {
      printf("Usage: %s <input file> [PM] [TW] [QI]\n", argv[0]);
      printf("exiting NIST DMIS Conformance Recorder\n");
      return 1;
    }
  printPM = findString2("PM", argv, argc);
  printTW = findString2("TW", argv, argc);
  useQ    = findString2("QI", argv, argc);
  if ((printPM + printTW + useQ) != (argc - 2))
    {
      printf("bad command arguments\n");
      printf("exiting NIST DMIS Conformance Recorder\n");
      return 1;
    }
  if ((printPM == 0) && (printTW == 0))
    {
      printPM = 1;
      printTW = 1;
    }
  test = fopen(argv[1], "r");
  if (test == 0)
    {
      printf("could not open file %s for reading\n", argv[1]);
      printf("exiting NIST DMIS Conformance Recorder\n");
      return 1;      
    }
  fclose(test);
  printf("parsing file %s\n", argv[1]);
  parseDmis(argv[1]);
  if (numErrors)
    {
      printf("%s contains %d parser error%s\n", argv[1], numErrors,
	     ((numErrors > 1) ? "s" : ""));
      printf("conformance statement not generated\n");
      printf("exiting NIST DMIS Conformance Recorder\n");
      return 1;
    }
  numErrors = 1; // so setLevsArray behaves
  check_inputFile(tree, 0);
  resetCurrentLevels();
  if (printPM)
    {
      setLevsArray('P', useQ, levs1);
      if (printTW)
	{
	  sprintf(levs2, "PM,%c, TW,%d%s",
		  levs1[3], currentLevels.tw, (levs1 + 4));
	}
      else
	sprintf(levs2, "%s", levs1);
    }
  else
    setLevsArray('T', useQ, levs2);
  insertConformanceStatement(argv[1], levs2);
  return 0;
}

/********************************************************************/

/*  resetCurrentLevels

Returned Value: none

Called by:  checkOneFile

This is choosing between using level 3 for PM and TW and using level 2
for CT when there is a choice. The need to choose arises because CT,2
is an alternative to PM,3 and TW,3 in the following case:

levels intFuncPtdataAtts_a_faLabel = {3,3,0,0,2,0,0,0,0};

If a PTDATA intrinsic function is used in a DMIS program, the
funcPtdataFlag will be set to 1. In that case, this may reset the level for
p and tw.
A. If currentLevels.p is less than 3
A1. If currentLevels.cs is 0, then currentLevels.p is set to 3 and
    levelForcers[0] is set to "intFuncPtdata".
A2. Otherwise, currentLevels.cs must be at least 2, so nothing is done.
B. If currentLevels.tw is less than 3
B1. If currentLevels.cs is 0, then currentLevels.tw is set to 3 and
    levelForcers[1] is set to "intFuncPtdata".
B2. Otherwise, currentLevels.cs must be at least 2, so nothing is done.

In other words, if funcPtdataFlag is 1 and the cs level is 2 or 3,
leave everything as is. But if funcPtdataFlag is 1 and the cs level is
0, increase p and tw to level 3 and do not require cs2.

*/

void resetCurrentLevels() /* NO ARGUMENTS */
{
  if ((funcPtdataFlag) && (currentLevels.cs == 0))
    {
      if (currentLevels.p < 3)
	{
	  currentLevels.p = 3;
	  levelForcers[0] = "intFuncPtdata";
	}
      if (currentLevels.tw < 3)
	{
	  currentLevels.tw = 3;
	  levelForcers[1] = "intFuncPtdata";
	}
    }
}

/********************************************************************/

/* setLevsArray

Returned Value: none

Called by:  checkOneFile

This prints in the levs array the minimum conformance module levels
required to execute a DMIS program.

For all modules except ipv and qis, if the command arguments have a
preferred level for a module and there are no conformance errors and
the preferred level is lower than that in currentLevels, then the
preferred level is reported. It is not clear that this can happen,
but it could happen if two modules both include some attribute or
subtype.

Only one of p or tw is printed in levs, and that is according to the
setting of pOrT.

Special treatment is given to ipv and qis because there are a lot of
attributes and subtypes common to the two modules.
1. If the command arguments have preferred levels for both ipv and qis
   and there are no errors and both preferred levels are less than or
   equal to the those in the currentLevels, then the preferred levels
   for ipv and qis are printed. This can happen if the following three
   conditions all hold.
   1a. The program includes one or more commands allowed by both qis
       and ipv at level 2 (CLMPID, for example).
   1b. The program includes one or more commands allowed only by qis
       and their levels are all 1 (DMEID, for example).
   1c. The program includes one or more commands allowed only by ipv
       and their levels are all 1 (MFGDEV, for example).
   In that case, the currentLevels for ipv and qis will both be 2, but
   there will be no errors if the preferred levels are 2 and 1 (either way).
2. Otherwise, if currentLevels.ipv and currentLevels.qis are both negative,
   that means that only commands included in both modules occurred in the
   program being tested. In this case the levels will be the same for
   ipv and qis. Either module could be reported but ipv requires
   fewer commands than qis. In this case
   2a. If useQ is true, then only qis is reported as being required.
   2b. Otherwise, only ipv is reported as being required.
3. Otherwise, if one module is positive and the other is negative, then
   only the one that is positive will be reported as being required.
4. Otherwise if the currentLevels values for both addenda are positive,
   then both addenda are reported as being required.

*/

void setLevsArray( /* ARGUMENTS                                       */
 char pOrT,        /* P = report prismatic, T = report thin-walled    */
 bool useQ,        /* true = report qis if choice; false = report ipv */
 char * levs)      /* array in which to record levels                 */
{
  int val;
  int qis;  // absolute value of currentLevels.qis
  int ipv;  // absolute value of currentLevels.ipv
  int k;    // counter for levs
  
  if (pOrT == 'P')
    {
      val = (((numErrors == 0) && (preferredLevels.p < currentLevels.p)) ?
	     preferredLevels.p : currentLevels.p);
      sprintf(levs, "PM,%d", val);
    }
  else //if (pOrT == 'T')
    {
      val = (((numErrors == 0) && (preferredLevels.tw < currentLevels.tw)) ?
	     preferredLevels.tw : currentLevels.tw);
      sprintf(levs, "TW,%d", val);
    }
  k = 4;
  if (currentLevels.rt)
    {
      val = (((numErrors == 0) && (preferredLevels.rt < currentLevels.rt)) ?
	     preferredLevels.rt : currentLevels.rt);
      sprintLevel(levs, &k, "RY", val);
    }
  if (currentLevels.mc)
    {
      val = (((numErrors == 0) && (preferredLevels.mc < currentLevels.mc)) ?
	     preferredLevels.mc : currentLevels.mc);
      sprintLevel(levs, &k, "MC", val);
    }
  if (currentLevels.cs)
    {
      val = (((numErrors == 0) && (preferredLevels.cs < currentLevels.cs)) ?
	     preferredLevels.cs : currentLevels.cs);
      sprintLevel(levs, &k, "CT", val);
    }
  ipv = ((currentLevels.ipv < 0) ? -currentLevels.ipv : currentLevels.ipv);
  qis = ((currentLevels.qis < 0) ? -currentLevels.qis : currentLevels.qis);
  if (preferredLevels.ipv && preferredLevels.qis && (numErrors == 0) &&
      (preferredLevels.ipv <= ipv) && (preferredLevels.qis <= qis))
    {
      sprintLevel(levs, &k, "QI", preferredLevels.qis);
      sprintLevel(levs, &k, "IP", preferredLevels.ipv);
    }
  else if (currentLevels.ipv < 0)
    {
      if (currentLevels.qis > 0)
	{
	  val = (((numErrors == 0) &&
		  (preferredLevels.qis < currentLevels.qis)) ?
		 preferredLevels.qis : currentLevels.qis);
	  sprintLevel(levs, &k, "QI", val);
	}
      else if (currentLevels.qis < 0)
	{
	  if (currentLevels.ipv != currentLevels.qis)
	    { // the two levels must be equal
	      fprintf(stderr, "bug 1 in setLevsArray\n");
	      exit(1);
	    }
	  else if (useQ)
	    sprintLevel(levs, &k, "QI", qis);
	  else
	    sprintLevel(levs, &k, "IP", ipv);
	}
      else
	{ // currentLevels.qis must not be 0 if currentLevels.ipv is negative
	  fprintf(stderr, "bug 2 in setLevsArray\n");
	  exit(1);
	}
    }
  else if (currentLevels.ipv > 0)
    {
      sprintLevel(levs, &k, "IP", currentLevels.ipv);
      if (currentLevels.qis > 0)
	sprintLevel(levs, &k, "QI", currentLevels.qis);
    }
  else if (currentLevels.qis > 0) // currentLevels.ipv is 0
    {
      val = (((numErrors == 0) && preferredLevels.qis &&
	      (preferredLevels.qis < currentLevels.qis)) ?
	     preferredLevels.qis : currentLevels.qis);
      sprintLevel(levs, &k, "QI", val);
    }
  if (currentLevels.unc)
    {
      val = (((numErrors == 0) && (preferredLevels.unc < currentLevels.unc)) ?
	     preferredLevels.unc : currentLevels.unc);
      sprintLevel(levs, &k, "MU", val);
    }
  if (currentLevels.sga)
    {
      val = (((numErrors == 0) && (preferredLevels.sga < currentLevels.sga)) ?
	     preferredLevels.sga : currentLevels.sga);
      sprintLevel(levs, &k, "SF", val);
    }
}

/********************************************************************/

/*  showLevelForcers

Returned Value: none

Called By: checkOneFile

This prints the level forcers (C++ classes or attributes) that
force the use of a particular level of an AP or addendum.

Almost always, the level forcer levels will be the same as those on
the first line of output. In the case of IP and QI, however, it is
possible for a level forcer level to be higher than the level on the
first line. For example, in the following sample output, level 1 of QI
is required, but the dmeidStm would have forced level 2 of QI (it didn't
because level 2 of IP includes the dmeidStm).

PM,1, QI,1, IP,2 
PM1 inputFile a_dmisFirstStatement
IP2 mfgdevStm
QI2 dmeidStm

*/

void showLevelForcers( /* ARGUMENTS                                       */
 char pOrT,            /* P = report prismatic, T = report thin-walled    */
 bool useQ)            /* true = report qis if choice; false = report ipv */
{
  if (pOrT == 'P')
    printf("PM%d %s\n",   currentLevels.p,
	   (levelForcers[0] ? levelForcers[0] : ""));
  else // if (pOrT == 'T')
    printf("TW%d %s\n",  currentLevels.tw,
	   (levelForcers[1] ? levelForcers[1] : ""));
  if (currentLevels.rt)
    printf("RY%d %s\n",  currentLevels.rt,
	   (levelForcers[2] ? levelForcers[2] : ""));
  if (currentLevels.mc)
    printf("MC%d %s\n",  currentLevels.mc,
	   (levelForcers[3] ? levelForcers[3] : ""));
  if (currentLevels.cs)
    printf("CT%d %s\n",  currentLevels.cs,
	   (levelForcers[4] ? levelForcers[4] : ""));
  if (currentLevels.ipv < 0)
    {
      if (currentLevels.qis > 0)
	printf("QI%d %s\n", currentLevels.qis,
	       (levelForcers[6] ? levelForcers[6] : ""));
      else if (currentLevels.qis < 0)
	{
	  if (useQ)
	    printf("QI%d %s\n", -currentLevels.qis,
		   (levelForcers[6] ? levelForcers[6] : "")); 
	  else
	    printf("IP%d %s\n", -currentLevels.ipv,
		   (levelForcers[5] ? levelForcers[5] : ""));
	}
    }
  else if (currentLevels.ipv > 0)
    {
      printf("IP%d %s\n", currentLevels.ipv,
	     (levelForcers[5] ? levelForcers[5] : ""));
      if (currentLevels.qis > 0)
	printf("QI%d %s\n", currentLevels.qis,
	       (levelForcers[6] ? levelForcers[6] : ""));
    }
  else if (currentLevels.qis > 0) // currentLevels.ipv is 0
    printf("QI%d %s\n", currentLevels.qis,
	   (levelForcers[6] ? levelForcers[6] : ""));
  if (currentLevels.unc)
    printf("MU%d %s\n", currentLevels.unc,
	   (levelForcers[7] ? levelForcers[7] : ""));
  if (currentLevels.sga)
    printf("SF%d %s\n", currentLevels.sga,
	   (levelForcers[8] ? levelForcers[8] : ""));
}

/********************************************************************/

/* sprintLevel

Returned Value: none

Called By: setLevsArray

This prints data for one more conformance module onto the end of
the levs string, which must have data for at least one module in it
already. For example, if mod is PM, and level is 2, this prints ", PM,2".
This updates the location, k, of the end of the string after it prints.

*/

void sprintLevel(  /* ARGUMENTS          */
 char * levs,      /* string to print in */
 int * k,          /* index for levs     */
 const char * mod, /* module name        */
 int level)        /* 1, 2, or 3         */
{
  sprintf((levs + *k), ", %s,%d", mod, level);
  *k = *k+6;
}

/********************************************************************/

/* testDmis

Returned Value: int
If there is an error in the arguments, then this returns 1.
Otherwise, it returns 0.

Called By: not called here. Intended to be called in some other file.

This checks that there are at least two arguments and returns 1 if not.
The first argument must be the name of a DMIS input file.

The second argument must be the code for a level of an AP. The
following codes are allowed. The meaning of each code is given
in parentheses.
PM1 (Prismatic 1)
PM2 (Prismatic 2)
PM3 (Prismatic 3)
TW1 (Thin Walled 1)
TW2 (Thin Walled 2)
TW3 (ThinWalled3)

If there are more than two arguments, each remaining argument must
be the name of a level of an addendum. The following codes are allowed.
The meaning of each code is given in parentheses. At most one level of
each addendum may be used, for a maximum of 7 more arguments.
RY1 (RotaryTable 1)
RY2 (RotaryTable 2)
RY3 (RotaryTable 3)
MC1 (Multi Carriage 1)
MC2 (Multi Carriage 2)
MC3 (Multi Carriage 3)
CT1 (Contact Scanning 1)
CT2 (Contact Scanning 2)
CT3 (Contact Scanning 3)
IP1 (IPV 1)
IP2 (IPV 2)
IP3 (IPV 3)
QI1 (QIS 1)
QI2 (QIS 2)
QI3 (QIS 3)
MU1 (measurement uncertainty 1)
MU2 (measurement uncertainty 2)
MU3 (measurement uncertainty 3)
SF1 (soft gaging 1)
SF2 (soft gaging 2)
SF3 (soft gaging 3)

This checks that there are at least two arguments. Then it calls
prepareLists, sets useQ, and calls checkOneFile.

*/

int testDmis(   /* ARGUMENTS                                   */
 int argc,      /* number of command arguments                 */
 char * argv[]) /* array of command name and command arguments */
{
  bool useQ;   // indicates a preference for ipv or qis if there is a choice

  if (argc < 3)
    {
      fprintf(stderr,
	  "Usage: %s <input file> <AP> [<addendum> ...]\n",
	      argv[0]);
      fprintf(stderr, "AP may be PM1, PM2, PM3, TW1, TW2, or TW3\n");
      fprintf(stderr, 
	      "addendum may be 0 or 1 from each of the following 7 sets\n");
      fprintf(stderr, 
	      "(RY1,RY2,RY3),(CT1,CT2,CT3),(MC1,MC2,MC3),(IP1,IP2,IP3)\n");
      fprintf(stderr, "(QI1,QI2,QI3),(MU1,MU2,MU3),(SF1,SF2,SF3)\n");
      return 1;
    }
  if (prepareLists((argc - 3), (argv + 2)) == false)
    {
      fprintf(stderr, "bad command arguments\n");
      return 1;
    }
  useQ = (findString2("IP1", argv, argc) ? false :
	  findString2("IP2", argv, argc) ? false :
	  findString2("IP3", argv, argc) ? false :
	  findString2("QI1", argv, argc) ? true :
	  findString2("QI2", argv, argc) ? true :
	  findString2("QI3", argv, argc) ? true : false);
  checkOneFile(argv[1], argv[2][0], useQ);
  return 0;
}

/********************************************************************/

} // namespace NDTU
